[2025-08-18] DOM Vulnerability

🦥 본문

정의 및 개념

클라이언트 측에서 동작하는 JS 코드가 DOM을 잘못 다루면서 발생하는 보안 취약점

  • DOM : Document Object Model. 브라우저가 HTML 문서를 관리하기 위해 사용하는 객체 모델로 웹 페이지에 대한 프로그래밍 인터페이

공격 방법: DOM Clobbering

공격자가 DOM 요소의 식별자 속성(id, name)을 이용해 자바스크립트에서 DOM 객체들의 속성을 변조하는 기법.

  • 예시
    • EX) 별도 변수 정의 없이 link1에 접근하면 해당 요소를 DOM에서 찾음

        <!DOCTYPE html>
        <html>
          <body>
            <a id="link1" href="https://host">host</a>
              
            <script>
              // 브라우저 콘솔에서 확인할 수 있는 상황 재현
              console.log(document.body);
              
              // link1은 DOM Element로 접근 가능
              console.log(link1);  
              
              // 자바스크립트 변수로 link1 재정의
              var link1 = 1;
              console.log(link1);  // 1 출력
            </script>
          </body>
        </html>
      
    • EX2) test 변수를 통한 DOM 객체 접근

        <!DOCTYPE html>
        <html>
          <body>
            <input id="test">
              
            <script>
              // 현재 body 출력
              console.log(document.body);
              
              // 처음 input의 value는 빈 문자열
              console.log(document.getElementById("test").value); // ''
              
              // id="test" 이므로 전역 객체 test가 자동으로 생성됨
              // 따라서 test.value를 직접 할당 가능
              test.value = 1234;
              console.log(test.value); // 1234
              
              // 실제 DOM 요소에도 값이 반영됨
              console.log(document.getElementById("test").value); // '1234'
            </script>
          </body>
        </html>
               
      
    • EX3) form1 변수를 통한 DOM 객체 접근

        <!DOCTYPE html>
        <html>
          <body>
            <form id="form1">
              <input name="firstName" value="John">
              <input type="submit">
            </form>
              
            <script>
              // form1 내부에 있는 input name="firstName" 접근
              console.log(document.getElementById("form1").firstName.value); // 'John'
              
              // form1 객체는 DOM에서 자동으로 전역 변수화됨
              // 또한 form1의 하위 input 요소는 name 속성을 통해 프로퍼티로 접근 가능
              form1.firstName.value = 'Alice';
              console.log(form1.firstName.value); // 'Alice'
              
              // 다시 DOM으로 확인해도 값 변경이 반영됨
              console.log(document.getElementById("form1").firstName.value); // 'Alice'
            </script>
          </body>
        </html>
              
      

글로별 변수 이름공간이나 요소 객체 속성은 미리 정의된 속성 / 함수(element.innerHTML , window.open)와 충돌

  • EX) innerHTML, removeChild와의 충돌

      <!DOCTYPE html>
      <html>
        <body>
          <form>
            <input id="innerHTML">
            <input type="text" name="removeChild">
          </form>
        
          <script>
            // form 내부 구조 확인
            console.log(document.forms[0].innerHTML);
            // 출력:
            // <input id="innerHTML">
            // <input type="text" name="removeChild">
        
            // 원래 removeChild는 DOM 메서드인데,
            // input의 name="removeChild" 때문에 동일한 프로퍼티가 생성됨
            // 따라서 메서드 대신 input 요소가 반환됨
            console.log(document.forms[0].removeChild);
            // 출력: <input type="text" name="removeChild">
          </script>
        </body>
      </html>
        
    
    • 기존에는 HTML 내의 콘텐츠를 String으로 반환하지만 객체 요소 자체를 반환

웹 애플리케이션이 미리 정의되지 않은 전역 변수에 접근한다면 공격자가 입력한 요소로 대체되어 반환될 수 있음

  • 브라우저는 HTML 요소의 idname을 전역 변수(window.속성)로 자동 등록
    • EX) <input id=”test”>가 있으면 window.test가 자동 생성. 하지만 개발자가 test라는 변수를 쓰려고 하는 데 정의하지 않은 경우 공격자가 HTML에서 id=”test”를 넣어버려서 DOM 요소 반환

form 등 요소에서 속성을 접근할 때 본래 속성 값이 아닌 삽입된 요소가 반환될 수 있음

  • <form> 객체에는 removeChild, submit, action 같은 속성이 있음
    • EX) 공격자가 <input name=”removeChild”> 를 넣으면 객체가 반환

실습: DOM Clobbering

<!-- HTML CODE IS INJECTED HERE -->
<!-- Your HTML here -->
<div id="config_status" style="white-space: pre;"></div>
<script>
if (window.CONFIG) {
    if (CONFIG.redirectUrl) {
        location.href = CONFIG.redirectUrl
    } else {
        document.write("<h1>redirectUrl is empty</h1>")
    }
} else {
    document.write("<h1>CONFIG is not defined.</h1>")
}
status = "CONFIG: " + (window.CONFIG||"NOT DEFINED")
status += "\r\n"
status += "CONFIG.redirectUrl: "
status += (window.CONFIG?window.CONFIG.redirectUrl||"NOT DEFINED":"NOT DEFINED")
config_status.textContent = status
</script>
  • 답)

      <a id="CONFIG" name="redirectUrl" href="javascript:alert(1);"></a> 
      <a id="CONFIG"></a>
    
    • window.CONFIG 까지는 첫 번째 줄로도 괜찮음
    • CONFIG.reidectURL인 경우는 첫 번째 줄로 안됨

    → HTMLCollection을 이용하여 빈 줄을 두면 다음과 같이 HTMLCollection 생성

      HTMLCollection(2) [a#CONFIG, a#CONFIG, CONFIG: a#CONFIG, redirectUrl: a#CONFIG]
    

    CONFIG.redirectURL을 통해 해당 객체에 접근 가능

공격 방법: DOM-based XSS

클라이언트 측 자바스크립트 코드가 사용자 입력을 DOM에 삽입하면서 발생하는 XSS 취약점

  • EX)

      var name_el = document.getElementById("name");
      name_el.innerHTML = `My name is ${decodeURIComponent(location.hash.slice(1))}.`;
    
    • 해시값에 https://host/domxss.html#<img src=@ onerror=alert(1)> 을 삽입하는 경우 XSS 발생
    • innerHTML 로 삽입하는 경우 <script> 태그는 실행 불가하므로 event 핸드러 사용

대응 방법

  • DOM Clobbering
    1. 간접 메소드 호출 및 접근자 사용(Function#call)
      • EX)

          // form.reset() 대신
          HTMLFormElement.prototype.reset.call(formElement);
                    
          // el.textContent = '' 대신
          Object.getOwnPropertyDescriptor(Node.prototype, 'textContent')
                .set.call(el, '');
                    
        
        • Third-party 라이브러리(jQuery)와 조합하면 취약점 발생 쉬움
    2. DOMPurify 같은 sanitization 라이브러리 사용 : id, name 같은 속성 제거
  • DOM-based XSS
    1. 동적으로 HTML 추가하는 행위 지양, innerHTML 대신 innerText 추가

Categories:

Updated:

Leave a comment